/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component;

import java.util.LinkedList;

import robocup.component.ControllerNumbers.INTERCEPTMODE;
import robocup.component.actions.Action;
import robocup.component.geometry.Vektor;
import robocup.component.situations.DribbleSituation;
import robocup.component.situations.GoalkeeperSituation;
import robocup.component.situations.InterceptBallSituation;
import robocup.component.situations.Movements;
import robocup.component.situations.PassSituation;
import robocup.component.situations.ScoreSituation;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * The prophet incorporates all calculations regarding predictions and 
 * expectations based on the current world model such as the calculation of
 * the ball-interception-point. The specific implementations are located in the 
 * different situation-classes (i.e. InterceptBallSituation), the Prophet itself 
 * rather serves as an interface, which is used by the tactic in order to 
 * delegates received requests to the specific situations. Hence it references
 * all situations and provides methods for accessing them. Implemented 
 * sitautions are:
 * <ul>
 *   <li>InterceptBallSituation</li>
 *   <li>PassSituation</li>
 *   <li>ScoreSituation</li>
 *   <li>GoalkeeperSituation</li>
 *   <li>DribbleSituation</li>
 * </ul>
 * 
 */
public class Prophet {

	/**
	 * The actual world model, on which all predictions are based.
	 */
	WorldModel world;

	RobocupAgent parent;

	/**
	 * The corresponding situations, which make the functionality of 
	 * the Prophet.
	 */
	private InterceptBallSituation interceptBallSituation;

	private PassSituation passSituation;

	private ScoreSituation scoreSituation;

	private GoalkeeperSituation goalkeeperSituation;

	private DribbleSituation newDribbleSituation;
	
	/**
	 * Movements contains pre-calculated movement information, which can be
	 * used for performant estimations of the players move-abilities.
	 */
	private Movements movements;

	/**
	 * The Prophet is instantiated with a reference of the complete agent
	 * (RobocupAgent - hence it is not usable for the Coach up to now).
	 * Note that the world-model should be instantiated as well as a part of
	 * the RobocupAgent, else the prophet and its sitautions won't work 
	 * correctly.
	 * 
	 * @param parent -
	 *            the agent-class
	 */
	public Prophet(RobocupAgent parent) {
		this.parent = parent;
	}

	
	/**
	 * This method instantiates all situations and the movement object. Only if 
	 * the agent is a goalie, the GoalkeeperSituation will be instantiated. 
	 */
	public void init() {
		this.world = this.parent.getWorld();
		this.movements = new Movements(this.world);
		this.interceptBallSituation = new InterceptBallSituation(this.world,
				this.movements);
		// this.interceptOppSituation = new
		// InterceptOpponentSituation(this.world,
		// this.movements, this.interceptBallSituation);
		if (this.world.isGoalie()) {
			this.goalkeeperSituation = new GoalkeeperSituation(this.world,
					this.movements, this);
		} else {
			this.goalkeeperSituation = null;
		}
		this.passSituation = new PassSituation(this.world, this,
				this.movements, this.parent.getSynchro());
		this.scoreSituation = new ScoreSituation(this.world, this.movements);
		this.newDribbleSituation = new DribbleSituation(this.world, this,
				this.movements);
	}


	/*
	 * ********************* BallInterception ********************************
	 */

	/**
	 * This method retrieves the point, where the agent expects the
	 * ball to be intercepted by the fastest agent.
	 * @return the interception-point (ip) as a Vektor
	 */
	public Vektor getNextBallInterceptionPoint() {
		return this.interceptBallSituation.getIp();
	}

	/**
	 * This method returns those players, which are expected to
	 * be the fastest to the ball (interception point). The returned
	 * value is a list of players, because there may exist more than one
	 * player sharing the same interception time.
	 * @return a list containing the fastest players to the interception point
	 */
	public LinkedList<Player> getFastestPlayerToBall() {
		return this.interceptBallSituation.getFastestPlayer();
	}

	
	/**
	 * This method returns the fastest enemy to the ball-interception point.
	 * 
	 * @return - the player of the opponent team, which is fastest to
	 * the interception point. It may return null, if none was found.
	 */
	public Player getFastestEnemyToBall() {
		for (Player p : this.getFastestPlayerToBall()) {
			if (!p.isFriend()) {
				return p;
			}
		}
		return null;
	}

	/**
	 * This method returns the fastest teammate to the ball-interception point.
	 * 
	 * @return - the player of the own team, which is fastest to
	 * the interception point. It may return null, if none was found.
	 */
	public Player getFastestTeammateToBall() {
		for (Player p : this.getFastestPlayerToBall()) {
			if (p.isFriend()) {
				return p;
			}
		}
		return null;
	}

	/**
	 * Retrieves a list of players, which is able to kick the ball.
	 * @return - all players, ahich have the ball located within their
	 * kickable distance.
	 */
	public LinkedList<Player> getPlayersThatCanKick() {
		return this.interceptBallSituation.getPlayersThatCanKick();
	}

	/**
	 * Returns the fastest time a player needs to intercept the ball.
	 * @return the time in cycles
	 */
	public int getInterceptionTime() {
		return this.interceptBallSituation.getInterceptionTime();
	}

	/**
	 * Returns the benefit (tactical value) of intercepting the ball
	 * in the current situation.
	 * @return the benefit as normalized value [0,1]
	 */
	public double getInterceptBallBenefit() {
		return this.interceptBallSituation.getBenefit();
	}

	
	/**
	 * This method checks, if the ball can be intercepted before leaving the 
	 * field border.
	 * @return <code>true</code>, if the ball leaves the field, before anyone 
	 * can intercept it. Else the returned value is <code>false</code>.
	 */
	public boolean ballOutBeforeIntercept() {
		return this.interceptBallSituation.ballOutBeforeIntercept();
	}

	
	/**
	 * This method retrieves the interception time to the ball 
	 * of a defined player.
	 * @param number - the trcot number of the corresponding player
	 * @param team - the team of the player. <code>True</code> refers to the own 
	 * 	team.
	 * @return the time in cycles, the corresponding player needs to intercept
	 * the ball.
	 */
	public int getInterceptionTime(int number, boolean team) {
		return this.interceptBallSituation.getInterceptionTime(number, team);
	}

	/**
	 * This method retrieves the interception time to the ball 
	 * of a defined player.
	 * @param p - the corresponding player
	 * @return the time in cycles, the corresponding player needs to intercept
	 * the ball.
	 */
	public int getInterceptionTime(Player p) {
		return this.getInterceptionTime(p.getNumber(), p.isFriend());
	}

	/**
	 * Retrieves the interception-times the <b>me</b>-player needs.
	 * The index of the array corresponds to a cycle, in which the ball
	 * can be intercepted, whereas the value at the index is the required
	 * amount of cycles (minimum) to get there.
	 * @return - an array with interception times
	 */
	public int[] getMyInterceptionTimes() {
		return this.interceptBallSituation.getMyInterceptionTimes();
	}

	/**
	 * This method retrieves the next optimal action, which should be
	 * executed in order to intercept the ball for the <b>me</b>-player.
	 * @return either a dash- or turn-action.
	 */
	public Action getMyNextInterceptAction() {
		return this.interceptBallSituation.getNextAction(this.world
				.getSelfRef());
	}

	/**
	 * This method retrieves the next (expected, if not self) action of the 
	 * given player when intercepting the ball.
	 * @param p - the considered player
	 * @return either a dash- or turn-action.
	 */
	public Action getNextInterceptAction(Player p) {
		return this.interceptBallSituation.getNextAction(p);
	}

	
	/**
	 * Returns true, if the <code>me</code>-player is expected to be
	 * fastest to ball.
	 * @return true, if interception-time of self is smaller than those of
	 * any other player
	 */
	public boolean iAmFastestToBall() {
		return this.interceptBallSituation.iAmFastestToBall();
	}

	/**
	 * Returns the ball possessing team, which is either
	 * <ul>
	 * 	<li>TEAM.WE</li>
	 * 	<li>TEAM.UNCERTAIN</li>
	 * 	<li>TEAM.OPPONENT</li>
	 * </ul>
	 * The returned value is of type <code>TEAM.UNCERTAIN</code>, if there are
	 * players of each time, which are nearly equally fast to the ball (little
	 * deviation may be allowed @see InterceptBallSituation).
	 * @return the expected ball possessing team
	 */
	public ControllerNumbers.TEAM inPossessionOfBall() {
		return this.interceptBallSituation.inPossessionOfBall();
	}

	/**
	 * Checks, if the ball is expected to be a goal of the opponents.
	 * @return tre, if the ball is expected to cross our goal line before
	 * anyone can intercept it. 
	 */
	public boolean ballWillGoIntoOwnGoal() {
		return this.interceptBallSituation.ballWillGoIntoOwnGoal();
	}

	/**
	 * This method retrieves the player, which was assumed the last kicking
	 * player.
	 * @return the player who kicked last
	 */
	public Player getLastKickingPlayer() {
		return this.interceptBallSituation.getLastKickingPlayer();
	}

	/**
	 * ************* Passing ***********************
	 */

	/**
	 * This method checks, if a pass can be performed, which is the case,
	 * if a kick was found, whose resulting ball is interceptable by a
	 * teammate.
	 * @return <code>true</code>, if a pass is detected, else 
	 * 	<code>false</code>
	 */
	public boolean canPass() {
		return this.passSituation.canPass();
	}

	/**
	 * This method retrieves the basic pass probability. Up to now, the returned
	 * value is constant 1.0. Note that this is the probability for a certain
	 * existing pass option, because these are currently not further analyzed.
	 * If <code>canPass</code> is <code>false</code> (i.e. no pass waas found), 
	 * passing is generally not possible.
	 * @return the probability [0,1] a certain pass may succeed
	 */
	public double getPassProb() {
		return 1.0;
	}

	/**
	 * This method retrieves the position where to pass best. This is the 
	 * expected interception point of the pass-receiver with the ball.
	 * @return the position as a <code>Vektor</code>
	 */
	public Vektor getPassPoint() {
		return this.passSituation.getResIP();
	}

	/**
	 * This method retrieves the expected receiver of the best found pass.
	 * @return the pass-receiving <code>player</code>
	 */
	public Player getPassTo() {
		return this.passSituation.getPassTo();
	}

	/**
	 * This method retrieves the corresponding (kick)-action for the
	 * best found pass.
	 * @return - the kick-action
	 */
	public Action getPassAction() {
		return this.passSituation.getBestAction();
	}

	/**
	 * This method retrieves a ball-instance, which is expected to exist in
	 * the next cycle after executing the best found pass with the corresponding
	 * kick-action.
	 * @return the ball after execution of the pass
	 */
	public Ball getResultingPassBall() {
		return this.passSituation.getResBall();
	}

	/**
	 * This method retrieves the benfit of executing the best possible
	 * found pass in the current situation.
	 * @return the benefit-value, which is in the interval [0,1]
	 */
	public double getPassBenefit() {
		return this.passSituation.getBenefit();
	}

	/**
	 * ************************ Scoring *********************************
	 */

	/**
	 * This method retrieves the benefit of scoring in the current situation.
	 * @return the benefit value, which is in the interval [0,1]
	 */
	public double getScoreBenefit() {
		return this.scoreSituation.getBenefit();
	}

	/**
	 * This method retrieves the (kick)-action for the best found scoring
	 * attemt.
	 * @return the corresponding kick-action
	 */
	public Action getScoreAction() {
		return this.scoreSituation.getBestAction();
	}

	
	/**
	 * Retrieves the next expected ball after executing the best found
	 * kick-action.
	 * @return the next ball, after executing the scoring attempt.
	 */
	public Ball getResultingScoreBall() {
		return this.scoreSituation.getResultingBall();
	}

	/**
	 * Checks whether the player should look towards the opponents goalie
	 * before attempting a goal-shot. If the goalie wasn't seen lately,
	 * a good estimation of a successful goal-shot is not possible.
	 * @return true, if the player should look towards the goalie before
	 * attempting to shoot a goal.
	 */
	public boolean shouldWatchGoalie() {
		return this.scoreSituation.isShouldWatchGoalie();
	}

	/**
	 * This method approximates the cycles for a player to reach a given point!
	 * Therefore it makes use of the <code>Movements</code>class, where
	 * reachable default distances for certain player types are stored.
	 * 
	 * @param player -
	 *            the considered player, either mate or opp
	 * @param target -
	 *            the position to go to!
	 * @param max -
	 *            the max-number of considered cycles
	 * @return the nums of cycles, with maximum <max>
	 */
	public int cyclesToReachPoint(Player player, Vektor target, int min, int max) {
		if (!player.exists())
			return Integer.MAX_VALUE;

		int index = (player.isFriend()) ? player.getNumber() - 1 : player
				.getNumber() + 10;

		double turn1 = this.world.getSelfRef().getMaxTurningAngle1();
		double turn2 = 180;

		Vektor pos = player.getPosition().cloned();
		Vektor spd = player.getSpeed().cloned();

		for (int i = 0; i < max; i++) {
			if (i >= min) {
				INTERCEPTMODE mode = this.movements.reaches(index, i, target,
						pos, player.getBodyDir(), turn1, turn2, 0.5);
				if (mode != INTERCEPTMODE.NO_INTERCEPT) // &&
					// mode != INTERCEPTMODE.DASH_BW &&
					// mode != INTERCEPTMODE.TURNING1_BW)
					return i;
			}
			pos.addToThis(spd);
			spd.mult(player.getPConf().PLAYER_DECAY);
		}
		return max;
	}

	/**
	 * This method checks, if a given player is fastest player to ball, i.e., if
	 * the player is inside the list of players that are fastest calculated by
	 * the interceptBall Situation and has the smallest interception time!
	 * 
	 * @param p -
	 *            the given player to check
	 * @return true if he is fastest
	 */
	public boolean isFastestPlayerToBall(Player p) {
		if (p == null)
			return false;
		LinkedList<Player> icpter = this.getFastestPlayerToBall();
		int time = Integer.MAX_VALUE;
		Player fastest = null;
		for (Player i : icpter) {
			int his_time = this
					.getInterceptionTime(p.getNumber(), p.isFriend());
			if (his_time < time) {
				time = his_time;
				fastest = i;
			}
		}
		if (fastest == null)
			return false;
		if (fastest.getNumber() == p.getNumber()
				&& fastest.isFriend() == p.isFriend())
			return true;
		return false;
	}

	/**
	 * Retrieves a reference to the <code>movements</code>-object. 
	 * @return <code>movements</code>
	 */
	public Movements getMovements() {

		return this.movements;
	}

	/**
	 * Sets a new <code>movements</code>-object.
	 * @param movements
	 *            The movements to set.
	 */
	public void setMovements(Movements movements) {

		this.movements = movements;
	}

	/***************************************************************************
	 * DribbleSituation
	 **************************************************************************/

	/**
	 * Retrieves the benefit of dribbling
	 * 
	 * @return the benefit of dribbling
	 */
	public double getDribbleBenefit() {
		return newDribbleSituation.getBenefit();
	}

	/**
	 * Retrieves the best Action for dribbling
	 * 
	 * @return the best Action for dribbling
	 */
	public Action getBestDribbleAction() {
		return newDribbleSituation.getBestAction();
	}

	/***************************************************************************
	 * GoalKeeperSituation
	 **************************************************************************/

	/**
	 * Retrieves the best action for the own goalie in the current
	 * situation.
	 * @return the best action for the keeper
	 */
	public Action getBestGoalieAction() {
		return this.goalkeeperSituation.getBestAction();
	}

	/**
	 * Returns the benefit of the goalie executing his default-behaviour.
	 * @return the benefit value, which lies within [0,1]
	 */
	public double getGoalieActionBenefit() {
		return this.goalkeeperSituation.getBenefit();
	}

	/**
	 * This method returns an identifier for the goalies abstract
	 * behaviour, which is determined in the GoalkeeperSituation.
	 * @return goalie sub state string
	 */
	public String getGoalieSubStateString() {
		return this.goalkeeperSituation.getSubStateString();
	}
}
